Background Jobs
Melodee uses Quartz.NET for background job scheduling. Jobs handle automated tasks like scanning libraries, moving files, updating the database, and maintaining data integrity.
Job Overview
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ LibraryInbound │ │ StagingAuto │ │ LibraryInsert │
│ ProcessJob │────▶│ MoveJob │────▶│ Job │
│ (every 10 min) │ │ (every 15 min) │ │ (chained) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │
└───────────── Media Ingestion Chain ───────────┘
Media Ingestion Jobs
These jobs form an automated chain that processes music from inbound through to the playable database.
LibraryInboundProcessJob
Scans the inbound library for new media files and processes them into staging.
| Property | Value |
|---|---|
| Default Schedule | Every 10 minutes (0 */10 * * * ?) |
| Setting Key | jobs.libraryProcess.cronExpression |
| Chains To | StagingAutoMoveJob (when scheduled) |
What It Does:
- Checks if the inbound library needs scanning (compares timestamps)
- Scans all subdirectories for supported audio files
- Parses ID3/Vorbis tags and extracts album artwork
- Validates and normalizes metadata (artist names, album titles, track numbers)
- Creates
melodee.jsonmetadata files for each discovered album - Moves processed albums to the staging directory
- Records scan history for monitoring
Skip Conditions:
- Inbound library path not configured
- Library is locked (
IsLocked=true) - No changes detected since last scan
StagingAutoMoveJob
Automatically moves validated albums from staging to storage.
| Property | Value |
|---|---|
| Default Schedule | Every 15 minutes (0 */15 * * * ?) |
| Setting Key | jobs.stagingAutoMove.cronExpression |
| Chains To | LibraryInsertJob (when scheduled) |
What It Does:
- Retrieves the staging library configuration
- Finds the target storage library (first unlocked storage library)
- Scans staging for albums with
AlbumStatus.Ok - Moves each qualifying album to the storage library
- Preserves album directory structure (Artist/Album format)
Skip Conditions:
- Staging library is locked
- No storage libraries configured
- All storage libraries are locked
- No albums with “Ok” status in staging
Why This Matters:
This job enables truly automatic music ingestion. Well-tagged albums that pass validation flow through the entire pipeline without manual intervention. Albums that need review remain in staging until manually approved.
StagingAlbumRevalidationJob
Periodically re-validates albums in staging that have invalid or unknown artists.
| Property | Value |
|---|---|
| Default Schedule | Weekly on Sunday at 3am (0 0 3 ? * SUN) |
| Setting Key | jobs.stagingAlbumRevalidation.cronExpression |
| Chains To | None (independent job) |
What It Does:
- Retrieves the staging library configuration
- Scans for albums with
HasInvalidArtistsorHasUnknownArtiststatus reasons - Re-queries the ArtistSearchEngineService for each album’s artist
- If the artist is now found in search engines, updates the album with the result
- Re-validates the album - if now valid, status becomes
AlbumStatus.Ok - Saves updated albums so StagingAutoMoveJob can move them to storage
Skip Conditions:
- Staging library is locked
- No albums with invalid/unknown artist status in staging
Why This Matters:
This job provides a self-healing mechanism for albums that were processed before their artist data became available in external sources (MusicBrainz, Spotify, etc.). When an artist is later added to these sources, this job automatically detects and fixes affected albums. This is particularly useful for:
- New or indie artists who may not have immediate search engine coverage
- Albums processed during search engine downtime
- Bulk imports where some artists weren’t recognized initially
Albums that become valid through revalidation will automatically flow through to storage via StagingAutoMoveJob, completing the ingestion pipeline without manual intervention.
LibraryInsertJob
Reads metadata from storage libraries and inserts records into the database, making music playable via API.
| Property | Value |
|---|---|
| Default Schedule | Daily at midnight (0 0 0 * * ?) |
| Setting Key | jobs.libraryInsert.cronExpression |
| Chains To | None (terminal job) |
What It Does:
- Scans all storage libraries for
melodee.jsonfiles - Filters to files modified since last scan (unless force mode)
- Loads and validates album metadata from each file
- Creates or finds existing Artist records (by name, MusicBrainz ID, or Spotify ID)
- Creates Album records with metadata (genres, release date, duration, etc.)
- Creates Song records with media file details (bitrate, duration, file hash)
- Creates Contributor records for performers, producers, and publishers
- Updates library aggregates (total albums, songs, duration)
- Records scan history
Special Handling:
- Duplicate albums are prefixed with
__duplicate_for manual review - Invalid
melodee.jsonfiles trigger reprocessing back to staging - Missing media files (referenced in JSON but not on disk) trigger reprocessing
Configuration Settings:
ProcessingMaximumProcessingCount: Maximum songs per run (0 = unlimited)ProcessingDuplicateAlbumPrefix: Prefix for duplicate album directoriesProcessingIgnoredPerformers/Publishers/Production: Names to exclude from contributors
Housekeeping Jobs
These jobs maintain data integrity and optimize system performance.
ArtistHousekeepingJob
Performs cleanup and maintenance on artist data.
| Property | Value |
|---|---|
| Default Schedule | Daily at midnight (0 0 0 * * ?) |
| Setting Key | jobs.artistHousekeeping.cronExpression |
What It Does:
- Removes orphaned artist records (artists with no albums)
- Updates artist statistics and aggregates
- Cleans up duplicate or merged artist entries
- Maintains artist relationship data
ArtistSearchEngineRepositoryHousekeepingJob
Updates and maintains the artist search index for fast lookups.
| Property | Value |
|---|---|
| Default Schedule | Daily at midnight (0 0 0 * * ?) |
| Setting Key | jobs.artistSearchEngineHousekeeping.cronExpression |
What It Does:
- Rebuilds artist search indexes
- Updates search terms and aliases
- Removes stale search entries for deleted artists
- Optimizes search performance
NowPlayingCleanupJob
Cleans up stale “now playing” entries from the database.
| Property | Value |
|---|---|
| Default Schedule | Every 5 minutes (0 */5 * * * ?) |
| Setting Key | Not configurable (fixed schedule) |
What It Does:
- Finds “now playing” entries older than the expected duration
- Removes stale entries that weren’t properly cleared
- Maintains accurate now-playing statistics
Data Update Jobs
These jobs update external data and maintain integrations.
ChartUpdateJob
Links chart entries (Billboard, etc.) to albums in the database.
| Property | Value |
|---|---|
| Default Schedule | Daily at 2 AM (0 0 2 * * ?) |
| Setting Key | jobs.chartUpdate.cronExpression |
What It Does:
- Scans all charts in the database
- Finds unlinked chart items (entries without album associations)
- Searches for matching albums by artist name and album title
- Creates links between chart items and albums
- Updates chart statistics
Why This Matters:
Charts are imported separately from the music library. This job connects chart data to your actual music, enabling features like “show albums from Billboard Top 100” filtering.
MusicBrainzUpdateDatabaseJob
Updates the local MusicBrainz database cache for artist and album lookups.
| Property | Value |
|---|---|
| Default Schedule | First of each month at noon (0 0 12 1 * ?) |
| Setting Key | jobs.musicbrainzUpdateDatabase.cronExpression |
What It Does:
- Downloads MusicBrainz database updates
- Imports new artist and release data
- Updates existing records with corrections
- Maintains the local search database
Configuration Settings:
searchEngine.musicbrainz.storagePath: Where to store the local databasesearchEngine.musicbrainz.importMaximumToProcess: Batch size limitsearchEngine.musicbrainz.importBatchSize: Records per batch
Job Chaining
When jobs are triggered by the scheduler (not manually), they automatically chain to the next job in sequence:
LibraryInboundProcessJob
│
▼ (on success, if work was done)
StagingAutoMoveJob
│
▼ (on success, if albums were moved)
LibraryInsertJob
│
▼ (terminal - no further chaining)
Note:
StagingAlbumRevalidationJobruns on its own schedule (weekly by default) to re-check albums that previously failed artist validation. It is not part of the automatic chain but is included when running./mcli library scan.
Automatic vs Manual Mode
Scheduled Triggers (Automatic Chaining):
- Jobs run via cron schedule
- On success, trigger the next job in chain
- Enables fully automatic media ingestion
Manual Triggers (No Chaining):
- Jobs triggered from the admin UI
- Do NOT chain to subsequent jobs
- Allows troubleshooting and selective execution
This design means:
- Drop files in inbound → music is playable within ~20 minutes (if validated)
- Manual triggers give full control for debugging or selective processing
CLI Full Scan Command
The CLI provides a single command that runs the entire ingestion pipeline in sequence:
./mcli library scan
This is equivalent to running all four jobs in order:
- LibraryInboundProcessJob
- StagingAlbumRevalidationJob
- StagingAutoMoveJob
- LibraryInsertJob
Use --force to ignore timestamps and reprocess everything:
./mcli library scan --force
See CLI Library Commands for full documentation.
Managing Jobs
Admin UI
Access the jobs dashboard at /admin/jobs to:
- View all scheduled jobs and their status
- See currently executing jobs
- View last execution time and next scheduled run
- Manually trigger any job
- Pause or resume individual jobs
- Pause or resume the entire scheduler
Disabling Jobs
To disable a job, set its cron expression to empty in the Settings:
- Navigate to
/admin/settings - Find the job’s cron expression setting
- Clear the value (leave empty)
- Save changes
Or set in the database:
UPDATE "Settings" SET "Value" = '' WHERE "Key" = 'jobs.libraryProcess.cronExpression';
Cron Expression Reference
Quartz cron expressions use 6-7 fields:
┌───────────── second (0-59)
│ ┌───────────── minute (0-59)
│ │ ┌───────────── hour (0-23)
│ │ │ ┌───────────── day of month (1-31)
│ │ │ │ ┌───────────── month (1-12)
│ │ │ │ │ ┌───────────── day of week (0-6, SUN-SAT)
│ │ │ │ │ │
* * * * * ?
Common Examples:
| Expression | Description |
|---|---|
0 */10 * * * ? |
Every 10 minutes |
0 */15 * * * ? |
Every 15 minutes |
0 0 * * * ? |
Every hour |
0 0 0 * * ? |
Daily at midnight |
0 0 2 * * ? |
Daily at 2 AM |
0 0 12 1 * ? |
First of each month at noon |
0 0 0 * * 0 |
Every Sunday at midnight |
Use Cron Expression Generator for complex schedules.
Troubleshooting
Job Not Running
- Check if the cron expression is set (empty = disabled)
- Check if the scheduler is paused (admin UI shows status)
- Check if the specific job is paused
- Review logs for errors during execution
Job Running But No Effect
- Check skip conditions (library locked, no changes, etc.)
- Review job logs for warnings
- Verify library paths are accessible
- Check file permissions on library directories
Jobs Taking Too Long
- Check
ProcessingMaximumProcessingCountsetting - Consider reducing batch sizes
- Review hardware resources (CPU, I/O)
- Check for locked files or network issues
Viewing Job Logs
Job execution is logged to the standard application log:
# Docker/Podman logs
docker logs melodee-blazor | grep -i "job"
# Or search for specific job
docker logs melodee-blazor | grep "LibraryInboundProcessJob"
Log messages include:
- Job start/stop times
- Items processed counts
- Skip reasons
- Errors and warnings
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.